/**
 * ******************************************************************************
 * 	Copyright 2011 Gerry Weirich
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 * 	$Id$
 * ******************************************************************************
 */
package ch.rgw.cybert

/*******************************************************/
/**
 * A Quant contains a question and an average answer value for this question in
 * a given Node
 * @author gerry
 *
 */
class Quant(questionID: Int, creator: String, phase: Int) {

  private var frequency: Int = 0;
  private var sum: Float = 0;
  private var ppv: Float = 1.0f
  private var npv: Float = 1.0f
  private val cluster = new Cluster

  // accessors
  def getQuestionID: Int = questionID;
  def getCreator = creator;
  def getPhase = phase
  def getPpv = ppv;
  def getNpv = npv
  def setPpv(value: Float) { ppv = if (value == 0f) 1f else value }
  def setNpv(value: Float) { npv = if (value == 0f) 1f else value }
  def getFrequency: Int = frequency;
  def setFrequency(f: Int) = frequency = f;
  def getSum: Float = sum;
  def setSum(s: Float) = sum = s;
  def getCluster = cluster.toList
  def setCluster(list: java.util.List[String]) = cluster.setRelations(list)

  def getValue: Float = sum / frequency;

  def compareEnvironment(that: Quant) = cluster.relationship(that.cluster)

  /** auxiliary constructor (copy constructor) */
  def this(that: Quant) = {
    this(that.getQuestionID, that.getCreator, that.getPhase)
    npv = that.npv;
    ppv = that.ppv;
    cluster.setRelations(that.cluster.toList)
  }
  /**
   * rebalancing is the process of assigning an adjusted value to a Quant or to remove a value respectively. When rebalancing, the Quant
   * also recalculates its environment (i.e. checks in which Nodes similar Quants exist)
   * @param v: the new Value to mix in
   * @param remove: if true the value will be removed, else it will be added
   */
  def rebalance(v: Float, remove: Boolean) {
    if (remove) {
      sum -= v;
      frequency -= 1;
    } else {
      sum += v;
      frequency += 1;
    }

    // The environment is defined as the cluster with positively answered Quants with the same
    // QuestionID
    cluster.clear
    if (getValue > 1) {
      Cybert.nodes.foreach(knol => {
        knol.getQuants.foreach(quant => {
          if (quant._1 == getQuestionID) {
            if (quant._2.getValue > 1) {
              cluster.addRelation(knol.getLabel)
            }
          }
        })
      })
    }
  }
  /**
   * Compare this Quant with an other and return a distance value
   * @param other the Quant to compare. Must have the same questionID
   * @return if the second value in the tuple is true, the first one is the distance.
   * The greater te number, the less alike are the Quants.
   * If the second value is false: Ignore this result
   */
  def computeDistanceTo(other: Quant): (Double, Boolean) = {
    require(other != null)
    // hyperbolic function to make larger values yield more similar results if both on same side, else linear function
    def transform(value: Double) = if (math.signum(other.getValue) == math.signum(getValue)) 4.0 / (2 * value) else value / 2.0;

    // adjust the raw values to consider ppv and npv.
    def adapt(value: Float): Double = if (value > 1) {
      transform(value * ppv)
    } else {
      if (value < -1) {
        transform(value * npv);
      } else {
        transform(value)
      }
    }
    // arbitrary cut-off to denote significant values
    def isSignificant(value: Float) = math.abs(value) > 0.9

    // consider only Quants with the same QuestionID that do have a significant value
    if (isSignificant(getValue) && isSignificant(other.getValue) && other.getQuestionID == getQuestionID) {
      val f1 = adapt(other.getValue)
      val f2 = adapt(getValue)
      (math.abs(f1 - f2), true)
    } else {
      (0.0, false)
    }
  }

  /** equals and hashCode overridden to make Maps and Lists work as expected */
  override def equals(that: Any) = questionID == that.asInstanceOf[Quant].getQuestionID;
  override def hashCode = questionID.hashCode

}